/*
 * NIST Healthcare Test Framework
 * TestFramework.java Jun 1, 2009
 *
 * This code was produced by the National Institute of Standards and
 * Technology (NIST). See the "nist.disclaimer" file given in the distribution
 * for information on the use and redistribution of this software.
 */
package gov.nist.healthcare.testframework;

import gov.nist.healthcare.configuration.testframework.TestFrameworkDocument;
import gov.nist.healthcare.configuration.testframework.TestFrameworkDocument.TestFramework.Protocols;
import gov.nist.healthcare.testframework.communication.mllp.server.MLLPEr7ServerThread;
import gov.nist.healthcare.testframework.communication.mllp.server.MLLPXmlServerThread;
import gov.nist.healthcare.testframework.communication.server.ServerContainer;
import gov.nist.healthcare.testframework.communication.soap.server.SoapServerThread;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

/**
 * This class is the main class of the test framework.
 * 
 * @author Sydney Henrard (NIST)
 */
public class TestFramework implements Runnable {

    private static final Logger logger = Logger.getLogger(TestFramework.class.getName());

    /* Contains the only instance */
    private static TestFramework instance;

    // /* Contains a MLLP XML messages listener */
    private ServerContainer mllpXml;
    //
    // /* Contains a MLLP ER7 messages listener */
    private ServerContainer mllpEr7;
    //
    /* Contains a SOAP messages listener */
    private ServerContainer soap;
    //
    // /* Contains a EbXML messages listener */
    // private EbXml ebXML;
    //
    /* Contains a secure MLLP XML messages listener */
    private ServerContainer mllpXmlSecure;

    /* Contains a secure MLLP ER7 messages listener */
    private ServerContainer mllpEr7Secure;

    /* Contains a secure SOAP messages listener */
    private ServerContainer soapSecure;

    /* Lock to suspend the thread */
    private final Object lock = new Object();

    /* SSL Context */
    private final SSLContext sslContext;

    // static Logger logger = Logger.getLogger(TestFramework.class.getName());

    /**
     * Private Construtor. Exists only to defeat instantiation.
     * 
     * @throws SecurityException
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     */
    private TestFramework() throws SecurityException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException,
            UnrecoverableKeyException, KeyManagementException {
        PropertyConfigurator.configure(TestFramework.class.getResource("/configuration/log4j.properties"));
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(
                TestFramework.class.getResourceAsStream("/security/keystore.jks"),
                "ebxmlrr".toCharArray());
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, "ebxmlrr".toCharArray());

        KeyStore trustStore = KeyStore.getInstance("jks");
        trustStore.load(
                TestFramework.class.getResourceAsStream("/security/truststore"),
                "ebxmlrr".toCharArray());
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        trustManagerFactory.init(trustStore);

        sslContext = SSLContext.getInstance("SSL");
        sslContext.init(keyManagerFactory.getKeyManagers(),
                trustManagerFactory.getTrustManagers(), new SecureRandom());

        // logger.info("Entering application.");
        // Environment.getInstance().getEventModel().addObserver(new
        // IOLogger());
        // Environment.getInstance().getEventModel().addObserver(new
        // ErrorLogger());
        // Environment.getInstance().getEventModel().addObserver(
        // new RawDataLogger());
    }

    /**
     * Gets the instance of the TestFramework.
     * 
     * @return the only existing instance
     */
    public static TestFramework getInstance() {
        if (instance == null) {
            synchronized (TestFramework.class) {
                try {
                    instance = new TestFramework();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return instance;
    }

    /**
     * Initialize the test framework. It will open the different ports to listen
     * for messages.
     * 
     * @param tfDoc
     *        a TestFrameworkDocument object that configures the test framework
     */
    public void init(TestFrameworkDocument tfDoc) {
        if (tfDoc.validate()) {
            Protocols protocols = tfDoc.getTestFramework().getProtocols();
            if (protocols != null) {
                if (protocols.getMLLPER7() != null) {
                    mllpEr7 = new ServerContainer(new MLLPEr7ServerThread(),
                            protocols.getMLLPER7().getPort().intValue(), false);
                    mllpEr7.start();
                }
                if (protocols.getMLLPXML() != null) {
                    mllpXml = new ServerContainer(new MLLPXmlServerThread(),
                            protocols.getMLLPXML().getPort().intValue(), false);
                    mllpXml.start();
                }
                if (protocols.getSOAPXML() != null) {
                    soap = new ServerContainer(new SoapServerThread(),
                            protocols.getSOAPXML().getPort().intValue(), false);
                    soap.start();
                }
                if (protocols.getMLLPER7SECURE() != null) {
                    mllpEr7Secure = new ServerContainer(
                            new MLLPEr7ServerThread(),
                            protocols.getMLLPER7SECURE().getPort().intValue(),
                            true);
                    mllpEr7Secure.start();
                }
                if (protocols.getMLLPXMLSECURE() != null) {
                    mllpXmlSecure = new ServerContainer(
                            new MLLPXmlServerThread(),
                            protocols.getMLLPXMLSECURE().getPort().intValue(),
                            true);
                    mllpXmlSecure.start();
                }
                if (protocols.getSOAPXMLSECURE() != null) {
                    soapSecure = new ServerContainer(new SoapServerThread(),
                            protocols.getSOAPXMLSECURE().getPort().intValue(),
                            true);
                    soapSecure.start();
                }
                Thread t = new Thread(this);
                t.start();
            }
        } else {
            // TODO: Log or exception
            System.err.println("Test Framework Configuration not valid");
        }
    }

    /**
     * Terminate the test framework. It closes all the connections.
     */
    public void terminate() {
        if (mllpXml != null) {
            mllpXml.close();
        }
        if (mllpEr7 != null) {
            mllpEr7.close();
        }
        if (soap != null) {
            soap.close();
        }
        if (mllpXmlSecure != null) {
            mllpXmlSecure.close();
        }
        if (mllpEr7Secure != null) {
            mllpEr7Secure.close();
        }
        if (soapSecure != null) {
            soapSecure.close();
        }
        // Release the lock
        synchronized (lock) {
            lock.notify();
        }
        logger.info("Terminate the test framework");
    }

    public void run() {
        // Suspend the current thread to avoid the test framework to exit
        // the terminate method will release the lock
        try {
            synchronized (lock) {
                lock.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Get the SSLContext to deal with secure connection
     * 
     * @return a SSLContext
     */
    public SSLContext getSSLContext() {
        return sslContext;
    }
}
